#!/usr/bin/env escript
%% -*- erlang -*
%%!

-include_lib("kernel/include/file.hrl").

main(Args) ->
   case lists:member("--help", Args) of
      true ->
         usage(),
         halt(0);
      false ->
         ok
   end,

   %% Get a string repr of build time
   BuiltTime = getTimeStr(),

   %% Get a string repr of first matching VCS changeset
   VcsInfo = vcsInfo([{git, ".git", "git describe --always --tags", "git status -s"}]),

   %% Check for force=1 flag to force a rebuild
   case lists:member("force=1", Args) of
      true ->
         rm("ebin/*.beam");
      false ->
         case filelib:is_file("ebin/npRMain.beam") of
            true -> rm("ebin/npRMain.beam");
            false -> io:fwrite("No beam files found.~n")
         end
   end,

   %% Add check for debug flag
   DebugFlag =
      case lists:member("debug", Args) of
         true -> debug_info;
         false -> undefined
      end,

   OtpInfo = string:strip(erlang:system_info(otp_release), both, $\n),

   %% Types dict:dict() and digraph:digraph() have been introduced in
   %% Erlang 17.
   %% At the same time, their counterparts dict() and digraph() are to be
   %% deprecated in Erlang 18. namespaced_types option is used to select
   %% proper type name depending on the OTP version used.
   %% Extract the system info of the version of OTP we use to compile npRMain
   %% NamespacedTypes =
   %%    case is_otp(OtpInfo, "^[0-9]+") of
   %%       true -> {d, namespaced_types};
   %%       false -> undefined
   %%    end,

   %% Compile all src/*.erl to ebin
   %% To not accidentally try to compile files like Mac OS X resource forks,
   %% we only look for npRMain source files that start with a letter.
   Opts = [
      DebugFlag,
      {outdir, "ebin"},
      {i, "include"},
      {d, 'BUILD_TIME', BuiltTime},
      {d, 'VCS_INFO', VcsInfo},
      {d, 'OTP_INFO', OtpInfo}
   ],
   case make:files(filelib:wildcard("src/*.erl"), Opts) of
      up_to_date ->
         ok;
      error ->
         io:format("Failed to compile eNpc files!\n"),
         halt(1)
   end,

   %% Make sure file:consult can parse the .app file
   case file:consult("ebin/eNpc.app") of
      {ok, _} ->
         ok;
      {error, Reason} ->
         io:format("Invalid syntax in ebin/eNpc.app: ~p\n", [Reason]),
         halt(1)
   end,

   %% Add ebin/ to our path
   true = code:add_path("ebin"),

   %% Run npRMain compile to do proper .app validation etc.
   %% and npRMain escriptize to create the npRMain script
   %% RebarArgs = Args -- ["debug"], %% Avoid trying to run 'debug' command
   % npRMain:main(["compile"] ++ RebarArgs),

   escriptize(),

   %% Finally, update executable perms for our script on *nix,
   %%  or write out script files on win32.
   case os:type() of
      {unix, _} ->
         [] = os:cmd("chmod u+x eNpc"),
         ok;
      {win32, _} ->
         writeWindowsScripts(),
         ok;
      _ ->
         ok
   end,

   %% Add a helpful message
   io:format(<<"Congratulations! You now have a self-contained script called"
   " \"eNpc\" in\n"
   "your current working directory. "
   "Place this script anywhere in your path\n"
   "and you can use eNpc to build native code for Erlang\n">>).

usage() ->
   io:format(<<"Usage: bootstrap [OPTION]...~n">>),
   io:format(<<"    force=1   unconditional build~n">>),
   io:format(<<"    debug     add debug information~n">>).

%% is_otp(OtpInfo, Regex) ->
%%    case re:run(OtpInfo, Regex, [{capture, none}]) of
%%       match -> true;
%%       nomatch -> false
%%    end.

rm(Path) ->
   NativePath = filename:nativename(Path),
   Cmd = case os:type() of
            {unix, _} -> "rm -f ";
            {win32, _} -> "del /q "
         end,
   [] = os:cmd(Cmd ++ NativePath),
   ok.

getTimeStr() ->
   {{Y, M, D}, {H, Min, S}} = erlang:localtime(),
   lists:flatten(io_lib:format("~B_~2.10.0B_~2.10.0B ~B:~2.10.0B:~2.10.0B", [Y, M, D, H, Min, S])).

vcsInfo([]) ->
   "No VCS info available.";
vcsInfo([{Id, Dir, VsnCmd, StatusCmd} | Rest]) ->
   case filelib:is_dir(Dir) of
      true ->
         Vsn = string:strip(os:cmd(VsnCmd), both, $\n),
         Status =
            case string:strip(os:cmd(StatusCmd), both, $\n) of
               [] ->
                  "";
               _ ->
                  "-dirty"
            end,
         lists:concat([Id, " ", Vsn, Status]);
      false ->
         vcsInfo(Rest)
   end.

writeWindowsScripts() ->
   CmdScript =
      "@echo off\r\n"
      "setlocal\r\n"
      "set rebarscript=%~f0\r\n"
      "escript.exe \"%rebarscript:.cmd=%\" %*\r\n",
   ok = file:write_file("eNpc.cmd", CmdScript).


escriptize() ->
   AppName = "eNpc",
   ScriptName = "eNpc",

   Files = loadEScriptFiles(AppName, "ebin", "*"),

   {ok, {"mem", ZipBin}} = zip:create("mem", Files, [memory]),
   Shebang = "#!/usr/bin/env escript\n",
   Comment = "%%\n",
   EmuArgs = io_lib:format("%%! -pa ~s/~s/ebin\n", [AppName, AppName]),
   Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]),
   ok = file:write_file(ScriptName, Script),

   %% Finally, update executable perms for our script
   {ok, #file_info{mode = Mode}} = file:read_file_info(ScriptName),
   ok = file:change_mode(ScriptName, Mode bor 8#00111).

loadEScriptFiles(AppName, Path, WildCard) ->
   FileNames = filelib:wildcard(WildCard, Path),
   Entries =
      lists:flatmap(
         fun(FN) ->
            FPath = filename:join(Path, FN),
            {ok, Contents} = file:read_file(FPath),
            ZipPath = filename:join(AppName, FPath),
            [{ZipPath, Contents} | dirEntries(ZipPath)]
         end,
         FileNames),
   usort(Entries).

%% Given a filename, return zip archive dir entries for each sub-dir.
%% Required to work around issues fixed in OTP-10071.
dirEntries(File) ->
   Dirs = dirs(File),
   [{Dir ++ "/", <<>>} || Dir <- Dirs].

%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"].
dirs(Dir) ->
   dirs1(filename:split(Dir), "", []).

dirs1([], _, Acc) ->
   lists:reverse(Acc);
dirs1([H | T], "", []) ->
   dirs1(T, H, [H]);
dirs1([H | T], Last, Acc) ->
   Dir = filename:join(Last, H),
   dirs1(T, Dir, [Dir | Acc]).

usort(List) ->
   lists:ukeysort(1, lists:flatten(List)).
